package com.karateca.columbiaimports.http;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This object provides methods to download data with an HTTP request. The
 * data can be downloaded with a POST or a GET HTTP method. The class holds
 * a session cookie to access the aplication. The session is extracted in the
 * first request to the server and it is added to any further requests.
 * When the connection is lost the object will try to reconnect. Each reconnect
 * attept will increase the waiting time by a factor of 1.5 until the maximum
 * number of attempts is reached.
 * @author Andres Dominguez
 */
public class HttpDownloaderImpl implements HttpDownloader
{
    /** connection timeout, in milliseconds */
    public static final int CONNECTION_TIMEOUT = 10000;
    /** Apache log. */
    private static final Log log = LogFactory.getLog(HttpDownloaderImpl.class);
    /** Maximum number of retires when the connection is lost */
    protected int maxRetryAttempts;
    /** These messages require a download retry */
    private List<String> retryMessageList;

    /**
     * Creates a new instance of HttpDownloaderImpl.
     * @param maxRetryAttempts maximum number of retires when the connection is lost.
     */
    public HttpDownloaderImpl(int maxRetryAttempts)
    {
        this.maxRetryAttempts = maxRetryAttempts;
        this.retryMessageList = new ArrayList<String>();
        this.retryMessageList.add("Ha ocurrido un error tipo 3. Favor intentar en breve");
        this.retryMessageList.add("Ha ocurrido un error tipo 1. Favor intentar en breve");
    }

    /**
     * Do an HTTP POST request. The object will try to reconnect if the connection
     * is lost.
     * @param pageUrl URL for the request.
     * @param postParams parameters sent in the querystring.
     * @return a string containing the data from the request.
     * @throws java.lang.Exception in case of error.
     */
    public String doPost(String pageUrl, String postParams) throws Exception
    {
        int retryCount = 0;
        int waitTime = 1000;
        URL url;
        URLConnection urlConn;
        DataOutputStream printout;
        InputStreamReader inputStreamReader;
        BufferedReader reader;
        StringBuilder stringBuilder;
        boolean downloadSuccessful = false;

        do
        {
            url = new URL(pageUrl);
            stringBuilder = new StringBuilder();
            try
            {
                url = new URL(pageUrl);
                urlConn = url.openConnection();
                urlConn.setConnectTimeout(CONNECTION_TIMEOUT);
                urlConn.setDoInput(true);
                urlConn.setDoOutput(true);
                urlConn.setUseCaches(false);

                // Send POST output.
                printout = new DataOutputStream(urlConn.getOutputStream());
                printout.writeBytes(postParams);
                printout.flush();
                printout.close();
                // Get response data.
                inputStreamReader = new InputStreamReader(urlConn.getInputStream());
                reader = new BufferedReader(inputStreamReader);

                String str;
                while (null != ((str = reader.readLine())))
                {
                    stringBuilder.append(str);
                }
                reader.close();
                inputStreamReader.close();
                // Check for error messages
                checkPage((stringBuilder.toString()));
                downloadSuccessful = true;
            }
            catch (Exception e)
            {
                retryCount++;
                if (retryCount > this.maxRetryAttempts)
                {
                    // maximum number reached, give up
                    log.error("Reached maximum retry attempts");
                    throw e;
                }
                log.warn("Error in link [" + pageUrl + "] " + e.getMessage());
                log.info("Retry " + retryCount + ", trying to reconnect in " + waitTime);
                Thread.sleep(waitTime);
                waitTime *= 1.5;
            }
        }
        while (downloadSuccessful == false);
        return stringBuilder.toString();
    }

    /**
     * Do an HTTP GET request. The object will try to reconnect if the connection
     * is lost.
     * @param pageUrl URL for the request.
     * @return a string containing the data from the request.
     * @throws java.lang.Exception in case of error.
     */
    public String doGet(String pageUrl) throws Exception
    {
        int retryCount = 0;
        int waitTime = 1000;
        URL url;
        URLConnection urlConn;
        InputStreamReader inputStreamReader;
        BufferedReader reader;
        StringBuilder stringBuilder;
        boolean downloadSuccessful = false;

        do
        {
            stringBuilder = new StringBuilder();
            try
            {
                url = new URL(pageUrl);
                urlConn = url.openConnection();
                urlConn.setConnectTimeout(CONNECTION_TIMEOUT);
                urlConn.setDoInput(true);
                urlConn.setUseCaches(false);

                // Get response data.
                inputStreamReader = new InputStreamReader(urlConn.getInputStream());
                reader = new BufferedReader(inputStreamReader);
                String str;
                while (null != ((str = reader.readLine())))
                {
                    // Check interruption
                    if (Thread.interrupted())
                    {
                        log.warn("Thread interrupted while downloading");
                        throw new InterruptedException("Thread interrupted while downloading");
                    }
                    stringBuilder.append(str);
                }
                reader.close();
                inputStreamReader.close();
                // Check for error messages
                checkPage((stringBuilder.toString()));
                downloadSuccessful = true;
            }
            catch (InterruptedException e)
            {
                // Interrupted exceptions must not be catched
                throw e;
            }
            catch (Exception e)
            {
                retryCount++;
                if (retryCount > this.maxRetryAttempts)
                {
                    // maximum number reached, give up
                    log.error("Reached maximum retry attempts");
                    throw e;
                }
                if (retryCount > 3)
                {
                    log.warn("Error in link [" + pageUrl + "] " + e.getMessage());
                    log.info("Retry " + retryCount + ", trying to reconnect in " + waitTime);
                }
                Thread.sleep(waitTime);
                waitTime *= 1.5;
            }
        }
        while (downloadSuccessful == false);
        return stringBuilder.toString();
    }

    /**
     * Check if the downloaded page contains one of the listed error messages.
     * Pages with error messages must be downloaded again.
     * @param page downloaded page.
     */
    protected void checkPage(String page) throws Exception
    {
        boolean error = false;
        String errMsg = null;

        // Check every error message
        for (String message : this.retryMessageList)
        {
            if (page.contains(message))
            {
                error = true;
                errMsg = "Found error message in page " + message;
                break;
            }
        }
        // Check if page ends with </html>
        if ((error == false) && (page.endsWith("</html>") == false))
        {
            error = true;
            errMsg = "Page does not end with </html> tag";
        }
        // check if there was an error
        if (error)
        {
            log.debug(errMsg);
            log.debug("Waiting 8 seconds");
            Thread.sleep(8000);
            throw new RuntimeException(errMsg);
        }
    }
}
